package com.dream.file;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.lang3.StringUtils;
import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;

import com.dream.file.chunk.ChunkUploadResult;
import com.dream.file.chunk.UploadChunkRepository;
import com.dream.file.chunk.UploadTaskRepository;

import lombok.extern.slf4j.Slf4j;

/**
 * FTP文件存储实现
 * 基于FTP协议实现文件存储、上传、下载等功能
 */
@Slf4j
public class FtpFileStorage implements FileStorage {

    /**
     * 存储配置信息
     */
    private final StorageConfig config;
    
    /**
     * 上传任务仓库
     */
    private final UploadTaskRepository uploadTaskRepository;
    
    /**
     * 上传分片仓库
     */
    private final UploadChunkRepository uploadChunkRepository;
    
    /**
     * 线程池，用于异步处理任务
     */
    private final ExecutorService executorService;
    
    /**
     * FTP服务器主机名
     */
    private final String hostname;
    
    /**
     * FTP服务器端口
     */
    private final int port;
    
    /**
     * FTP用户名
     */
    private final String username;
    
    /**
     * FTP密码
     */
    private final String password;

    /**
     * 构造方法
     * 
     * @param config 存储配置
     * @param uploadTaskRepository 上传任务仓库
     * @param uploadChunkRepository 上传分片仓库
     */
    public FtpFileStorage(StorageConfig config,
                          UploadTaskRepository uploadTaskRepository,
                          UploadChunkRepository uploadChunkRepository) {
        this.config = config;
        this.uploadTaskRepository = uploadTaskRepository;
        this.uploadChunkRepository = uploadChunkRepository;
        this.executorService = Executors.newFixedThreadPool(10);
        
        // 解析endpoint作为FTP服务器地址，格式：ftp://hostname:port
        String endpoint = config.getEndpoint();
        endpoint = endpoint.replaceFirst("^ftp://", "");
        
        if (endpoint.contains(":")) {
            String[] parts = endpoint.split(":");
            this.hostname = parts[0];
            this.port = Integer.parseInt(parts[1]);
        } else {
            this.hostname = endpoint;
            this.port = 21; // 默认FTP端口
        }
        
        this.username = config.getAssessKey();
        this.password = config.getSecretKey();
        
        // 测试连接
        testConnection();
    }
    
    /**
     * 测试FTP连接
     */
    private void testConnection() {
        FTPClient ftpClient = new FTPClient();
        try {
            ftpClient.connect(hostname, port);
            int replyCode = ftpClient.getReplyCode();
            
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                throw new FileStorageException("FTP服务器拒绝连接");
            }
            
            boolean success = ftpClient.login(username, password);
            if (!success) {
                throw new FileStorageException("FTP登录失败，用户名或密码错误");
            }
            
            ftpClient.logout();
            log.info("FTP连接测试成功: {}", hostname);
        } catch (IOException e) {
            throw new FileStorageException("FTP连接测试失败", e);
        } finally {
            try {
                if (ftpClient.isConnected()) {
                    ftpClient.disconnect();
                }
            } catch (IOException e) {
                log.error("关闭FTP连接失败", e);
            }
        }
    }
    
    /**
     * 获取FTP客户端连接
     * 
     * @return FTP客户端
     */
    private FTPClient getFtpClient() throws IOException {
        FTPClient ftpClient = new FTPClient();
        ftpClient.connect(hostname, port);
        
        int replyCode = ftpClient.getReplyCode();
        if (!FTPReply.isPositiveCompletion(replyCode)) {
            ftpClient.disconnect();
            throw new FileStorageException("FTP服务器拒绝连接");
        }
        
        boolean success = ftpClient.login(username, password);
        if (!success) {
            ftpClient.disconnect();
            throw new FileStorageException("FTP登录失败，用户名或密码错误");
        }
        
        // 设置文件传输模式为二进制
        ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
        
        // 设置被动模式，解决数据连接问题
        ftpClient.enterLocalPassiveMode();
        
        // 设置UTF-8编码
        ftpClient.setControlEncoding("UTF-8");
        
        return ftpClient;
    }
    
    /**
     * 确保目录存在，如果不存在则创建
     * 
     * @param ftpClient FTP客户端
     * @param path 目录路径
     * @throws IOException IO异常
     */
    private void ensureDirectoryExists(FTPClient ftpClient, String path) throws IOException {
        if (StringUtils.isEmpty(path)) {
            return;
        }
        
        // 去除开头的/
        path = path.replaceFirst("^/", "");
        
        // 切换到根目录
        ftpClient.changeWorkingDirectory("/");
        
        // 逐级创建目录
        String[] dirs = path.split("/");
        for (String dir : dirs) {
            if (StringUtils.isEmpty(dir)) {
                continue;
            }
            
            boolean dirExists = ftpClient.changeWorkingDirectory(dir);
            if (!dirExists) {
                boolean created = ftpClient.makeDirectory(dir);
                if (!created) {
                    throw new FileStorageException("无法在FTP服务器上创建目录: " + dir);
                }
                ftpClient.changeWorkingDirectory(dir);
            }
        }
    }

    /**
     * 上传文件
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @return 访问URL
     */
    @Override
    public String upload(InputStream inputStream, String path, String fileName) {
        FTPClient ftpClient = null;
        try {
            ftpClient = getFtpClient();
            
            // 确保目录存在
            ensureDirectoryExists(ftpClient, path);
            
            // 构建FTP路径
            String ftpPath = buildPath(path);
            ftpClient.changeWorkingDirectory(ftpPath);
            
            // 上传文件
            boolean success = ftpClient.storeFile(fileName, inputStream);
            if (!success) {
                throw new FileStorageException("FTP上传文件失败: " + ftpClient.getReplyString());
            }
            
            log.info("FTP上传文件成功: {}/{}", ftpPath, fileName);
            return generateUrl(path, fileName);
        } catch (IOException e) {
            throw new FileStorageException("FTP上传文件失败", e);
        } finally {
            closeQuietly(ftpClient);
            closeQuietly(inputStream);
        }
    }

    /**
     * 分片上传文件
     * 支持大文件的分片上传
     * 注意：标准FTP协议不直接支持分片上传，此处为模拟实现
     * 
     * @param inputStream 文件输入流
     * @param path 存储路径
     * @param fileName 文件名
     * @param chunkNumber 当前分片序号
     * @param totalChunks 总分片数
     * @return 分片上传结果
     */
    @Override
    public ChunkUploadResult uploadChunk(InputStream inputStream, String path, String fileName,
                                         int chunkNumber, int totalChunks) {
        // FTP协议不直接支持分片上传，需要在本地临时保存分片，然后合并后再上传
        try {
            // 获取或创建上传任务ID
            String uploadId = getOrCreateMultipartUploadId(path, fileName);
            
            // 创建临时目录存储分片
            Path tempDir = Paths.get(System.getProperty("java.io.tmpdir"), "ftp-chunks", uploadId);
            if (!Files.exists(tempDir)) {
                Files.createDirectories(tempDir);
            }
            
            // 保存分片到临时文件
            Path chunkFile = tempDir.resolve(chunkNumber + ".part");
            Files.copy(inputStream, chunkFile, java.nio.file.StandardCopyOption.REPLACE_EXISTING);
            
            // 创建结果对象
            ChunkUploadResult result = new ChunkUploadResult();
            result.setUploadId(uploadId);
            result.setEtag("chunk-" + chunkNumber);
            result.setPartNumber(chunkNumber);
            result.setCompleted(chunkNumber == totalChunks);
            
            // 如果是最后一个分片，合并并上传
            if (result.isCompleted()) {
                // 合并所有分片为一个文件
                Path mergedFile = tempDir.resolve(fileName);
                mergeChunks(tempDir, mergedFile, totalChunks);
                
                // 上传合并后的文件
                try (InputStream mergedFileStream = Files.newInputStream(mergedFile)) {
                    upload(mergedFileStream, path, fileName);
                }
                
                // 清理临时文件
                cleanupTempFiles(tempDir);
            }
            
            return result;
        } catch (Exception e) {
            throw new FileStorageException("FTP分片上传失败", e);
        } finally {
            closeQuietly(inputStream);
        }
    }

    /**
     * 合并分片文件
     * 
     * @param tempDir 临时目录
     * @param targetFile 目标文件
     * @param totalChunks 总分片数
     * @throws IOException IO异常
     */
    private void mergeChunks(Path tempDir, Path targetFile, int totalChunks) throws IOException {
        try (OutputStream out = Files.newOutputStream(targetFile)) {
            byte[] buffer = new byte[1024 * 1024]; // 1MB缓冲区
            for (int i = 1; i <= totalChunks; i++) {
                Path chunkFile = tempDir.resolve(i + ".part");
                if (!Files.exists(chunkFile)) {
                    throw new FileStorageException("找不到分片文件: " + chunkFile);
                }
                
                try (InputStream in = Files.newInputStream(chunkFile)) {
                    int bytesRead;
                    while ((bytesRead = in.read(buffer)) != -1) {
                        out.write(buffer, 0, bytesRead);
                    }
                }
            }
        }
    }
    
    /**
     * 清理临时文件
     * 
     * @param tempDir 临时目录
     */
    private void cleanupTempFiles(Path tempDir) {
        try {
            Files.walk(tempDir)
                .sorted(java.util.Comparator.reverseOrder())
                .forEach(path -> {
                    try {
                        Files.delete(path);
                    } catch (IOException e) {
                        log.warn("删除临时文件失败: {}", path, e);
                    }
                });
        } catch (IOException e) {
            log.warn("清理临时文件失败", e);
        }
    }

    /**
     * 下载文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件输入流
     */
    @Override
    public InputStream download(String path, String fileName) {
        try {
            FTPClient ftpClient = getFtpClient();
            
            // 切换到目标目录
            String ftpPath = buildPath(path);
            boolean pathExists = ftpClient.changeWorkingDirectory(ftpPath);
            if (!pathExists) {
                throw new FileStorageException("FTP路径不存在: " + ftpPath);
            }
            
            // 验证文件是否存在
            FTPFile[] files = ftpClient.listFiles(fileName);
            if (files.length == 0) {
                closeQuietly(ftpClient);
                throw new FileStorageException("文件不存在: " + ftpPath + "/" + fileName);
            }
            
            // 为了解决FTP连接关闭问题，这里将文件内容读取到内存中
            ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
            boolean success = ftpClient.retrieveFile(fileName, outputStream);
            
            // 关闭FTP连接
            closeQuietly(ftpClient);
            
            if (!success) {
                throw new FileStorageException("下载文件失败");
            }
            
            // 返回内存中的数据作为输入流
            return new ByteArrayInputStream(outputStream.toByteArray());
        } catch (IOException e) {
            throw new FileStorageException("下载文件失败", e);
        }
    }

    /**
     * 范围下载文件
     * 注意：标准FTP协议不直接支持范围下载，此处为模拟实现
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @param start 开始位置（字节偏移量）
     * @param end 结束位置（字节偏移量）
     * @return 文件部分内容的输入流
     */
    @Override
    public InputStream downloadRange(String path, String fileName, long start, long end) {
        try {
            // 下载完整文件
            InputStream fullFileStream = download(path, fileName);
            
            // 跳过开始位置前的字节
            fullFileStream.skip(start);
            
            // 读取指定范围的数据
            byte[] buffer = new byte[(int) (end - start + 1)];
            fullFileStream.read(buffer);
            
            // 关闭原始流
            fullFileStream.close();
            
            // 返回内存中的范围数据
            return new ByteArrayInputStream(buffer);
        } catch (IOException e) {
            throw new FileStorageException("范围下载失败", e);
        }
    }

    /**
     * 删除文件
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 是否删除成功
     */
    @Override
    public boolean delete(String path, String fileName) {
        FTPClient ftpClient = null;
        try {
            ftpClient = getFtpClient();
            
            // 切换到目标目录
            String ftpPath = buildPath(path);
            boolean pathExists = ftpClient.changeWorkingDirectory(ftpPath);
            if (!pathExists) {
                return false; // 路径不存在视为删除成功
            }
            
            // 删除文件
            boolean success = ftpClient.deleteFile(fileName);
            if (success) {
                log.info("FTP删除文件成功: {}/{}", ftpPath, fileName);
            } else {
                log.warn("FTP删除文件失败: {}/{}", ftpPath, fileName);
            }
            
            return success;
        } catch (IOException e) {
            log.error("FTP删除文件出错", e);
            return false;
        } finally {
            closeQuietly(ftpClient);
        }
    }

    /**
     * 获取文件元数据信息
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 文件元数据
     */
    @Override
    public FileMetadata getMetadata(String path, String fileName) {
        FTPClient ftpClient = null;
        try {
            ftpClient = getFtpClient();
            
            // 切换到目标目录
            String ftpPath = buildPath(path);
            boolean pathExists = ftpClient.changeWorkingDirectory(ftpPath);
            if (!pathExists) {
                throw new FileStorageException("FTP路径不存在: " + ftpPath);
            }
            
            // 获取文件信息
            FTPFile[] files = ftpClient.listFiles(fileName);
            if (files.length == 0) {
                throw new FileStorageException("文件不存在: " + ftpPath + "/" + fileName);
            }
            
            FTPFile file = files[0];
            
            // 构建元数据
            FileMetadata metadata = new FileMetadata();
            metadata.setFileName(fileName);
            metadata.setPath(path);
            metadata.setSize(file.getSize());
            metadata.setContentType(getContentType(fileName));
            metadata.setLastModified(file.getTimestamp().toInstant());
            metadata.setEtag(file.getName() + "-" + file.getSize() + "-" + file.getTimestamp().getTimeInMillis());
            
            // FTP不支持自定义元数据
            metadata.setMetadata(new HashMap<>());
            
            return metadata;
        } catch (IOException e) {
            throw new FileStorageException("获取文件元数据失败", e);
        } finally {
            closeQuietly(ftpClient);
        }
    }

    /**
     * 构建路径
     * 
     * @param relativePath 相对路径
     * @return 完整FTP路径
     */
    private String buildPath(String relativePath) {
        String basePath = StringUtils.trimToEmpty(config.getBasePath());
        relativePath = StringUtils.trimToEmpty(relativePath);
        
        String fullPath = String.format("%s/%s", basePath, relativePath)
                .replaceAll("//+", "/")
                .replaceFirst("^/", "");
        
        return "/" + fullPath;
    }

    /**
     * 生成访问URL
     * 
     * @param path 文件路径
     * @param fileName 文件名
     * @return 访问URL
     */
    private String generateUrl(String path, String fileName) {
        // FTP URL格式: ftp://username:password@hostname:port/path/fileName
        // 注意：实际应用中不应在URL中包含密码，而应使用其他方式进行认证
        String basePath = StringUtils.trimToEmpty(config.getBasePath());
        path = StringUtils.trimToEmpty(path);
        
        String fullPath = String.format("%s/%s", basePath, path)
                .replaceAll("//+", "/")
                .replaceFirst("^/", "");
        
        return String.format("ftp://%s:%d/%s/%s", 
                hostname, 
                port, 
                fullPath,
                fileName);
    }

    /**
     * 获取文件内容类型
     * 
     * @param fileName 文件名
     * @return 内容类型/MIME类型
     */
    private String getContentType(String fileName) {
        // 简单实现，实际项目中可以使用更完善的方法
        String ext = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();
        return switch (ext) {
            case "jpg", "jpeg" -> "image/jpeg";
            case "png" -> "image/png";
            case "pdf" -> "application/pdf";
            default -> "application/octet-stream";
        };
    }

    /**
     * 获取或创建分片上传ID
     * 
     * @param path 存储路径
     * @param fileName 文件名
     * @return 上传ID
     */
    private String getOrCreateMultipartUploadId(String path, String fileName) {
        // 在实际项目中，应该将上传ID持久化到数据库中
        // 这里简单使用UUID模拟
        return UUID.randomUUID().toString();
    }
    
    /**
     * 安全关闭FTP客户端
     * 
     * @param ftpClient FTP客户端
     */
    private void closeQuietly(FTPClient ftpClient) {
        if (ftpClient != null && ftpClient.isConnected()) {
            try {
                ftpClient.logout();
                ftpClient.disconnect();
            } catch (IOException e) {
                log.warn("关闭FTP连接失败", e);
            }
        }
    }
    
    /**
     * 安全关闭输入流
     * 
     * @param inputStream 输入流
     */
    private void closeQuietly(InputStream inputStream) {
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                log.warn("关闭输入流失败", e);
            }
        }
    }
} 